package editorsvg.editor.formas;

import editorsvg.editor.atributos.AtrString;
import editorsvg.propiedades.GrupoPropiedades;
import editorsvg.propiedades.PropiedadTextoMultilinea;
import java.awt.Shape;
import java.awt.geom.Path2D;
import java.awt.geom.Point2D;
import java.util.LinkedList;
import org.apache.batik.parser.ParseException;
import org.apache.batik.parser.PathHandler;
import org.apache.batik.parser.PathParser;

/**
 * Forma que representa un trayecto.
 * @author Jorge Berjano
 */
public class Trayecto extends Forma {

    private AtrString datos = new AtrString();
    
    private LinkedList<NodoTrayecto> nodos = new LinkedList<NodoTrayecto>();
    private Path2D path = new Path2D.Double();
    private boolean regenerarNodos;
    private static GrupoPropiedades propiedadesForma;    

    @Override
    public Object clone() throws CloneNotSupportedException {
        Trayecto clon = (Trayecto) super.clone();
        clon.datos = (AtrString) datos.clone();
        clon.nodos = (LinkedList<NodoTrayecto>) nodos.clone();
        clon.path = (Path2D) path.clone();
        return clon;
    }

    @Override
    public String getNombreTag() {
        return "path";
    }

    @Override
    public GrupoPropiedades getPropiedades() {
        if (propiedadesForma == null) {
            propiedadesForma = new GrupoPropiedades("Trayecto");
            propiedadesForma.agregarPropiedad(new PropiedadTextoMultilinea("datos", "d"));
        }
        return propiedadesForma;
    }

    @Override
    public Shape getShape() {
        return path;
    }

    @Override
    public void actualizar() {
        // TODO: solucionar esto
        regenerarNodos = true;
        if (regenerarNodos) {
            regenerarNodos = false;
            parsearDatos();
            actualizarPath();
        }
    }

    public AtrString datos() {
        return datos;
    }
    
    public void setDatos(String valor) {
        datos.setValor(valor);
    }
    
    public String getDatos() {
        return datos.getValor();
    }

    public void agregar(NodoTrayecto nodo) {
        nodos.add(nodo);
    }

    public void actualizarPath() {
        path.reset();
        for (NodoTrayecto nodo : nodos) {
            nodo.agregarAlPath(path);
        }
    }

    public NodoTrayecto getNodo(int indice) {
        if (indice >= 0 && indice < nodos.size()) {
            return nodos.get(indice);
        } else {
            return null;
        }
    }

    /**
     * Genera el atributo datos a partir de la lista de nodos.
     */
    public void generarDatos() {
        StringBuilder builder = new StringBuilder();
        for (NodoTrayecto nodo : nodos) {
            builder.append(nodo);
            builder.append("\n");
        }
        datos.setValor(builder.toString());
    }

    /**
     * Parsea el atributo datos generando la lista de nodos.
     */
    private void parsearDatos() {
        try {
            nodos.clear();
            if (datos.getValor() == null) {
                return;
            }

            PathParser parser = new PathParser();
            PathHandler handler = new PathHandler() {

                private float cx,  cy; // Posicion actual

                @Override
                public void startPath() throws ParseException {
                }

                @Override
                public void endPath() throws ParseException {
                }

                @Override
                public void movetoAbs(float x, float y) throws ParseException {
                    cx = x;
                    cy = y;
                    NodoTrayecto nodo = new NodoTrayecto(NodoTrayecto.TipoNodo.MOVER, new Point2D.Double(x, y));
                    agregar(nodo);
                }

                @Override
                public void movetoRel(float x, float y) throws ParseException {
                    movetoAbs(cx + x, cy + y);
                }

                @Override
                public void linetoAbs(float x, float y) throws ParseException {
                    cx = x;
                    cy = y;
                    NodoTrayecto nodo = new NodoTrayecto(NodoTrayecto.TipoNodo.RECTA, new Point2D.Double(cx, cy));
                    agregar(nodo);
                }

                @Override
                public void linetoRel(float x, float y) throws ParseException {
                    linetoAbs(cx + x, cy + y);
                }

                @Override
                public void linetoHorizontalAbs(float x) throws ParseException {
                    linetoAbs(x, cy);
                }

                @Override
                public void linetoHorizontalRel(float x) throws ParseException {
                    linetoAbs(cx + x, cy);
                }

                @Override
                public void linetoVerticalAbs(float y) throws ParseException {
                    linetoAbs(cx, y);
                }

                @Override
                public void linetoVerticalRel(float y) throws ParseException {
                    linetoAbs(cx, cy + y);
                }

                @Override
                public void curvetoCubicAbs(float x1, float y1, float x2, float y2, float x, float y) throws ParseException {
                    cx = x;
                    cy = y;
                    NodoTrayecto nodo = new NodoTrayecto(NodoTrayecto.TipoNodo.CURVA_CUBICA,
                            new Point2D.Double(x, y),
                            new Point2D.Double(x1, y1),
                            new Point2D.Double(x2, y2));
                    agregar(nodo);
                }

                @Override
                public void curvetoCubicRel(float x1, float y1, float x2, float y2, float x, float y) throws ParseException {
                    curvetoCubicAbs(cx + x1, cy + y1, cx + x2, cy + y2, cx + x, cy + y);
                }

                @Override
                public void curvetoCubicSmoothAbs(float x2, float y2, float x, float y) throws ParseException {
                    cx = x;
                    cy = y;
                    NodoTrayecto ultimoNodo = nodos.getLast();
                    Point2D simetrico = ultimoNodo.getTipo() == NodoTrayecto.TipoNodo.CURVA_CUBICA ? ultimoNodo.getSimetricaPosicionControl2() : null;
                    float x1 = (float) (simetrico != null ? simetrico.getX() : x);
                    float y1 = (float) (simetrico != null ? simetrico.getY() : y);
                    curvetoCubicAbs(x1, y1, x2, y2, x, y);
                }

                @Override
                public void curvetoCubicSmoothRel(float x2, float y2, float x, float y) throws ParseException {
                    curvetoCubicSmoothAbs(cx + x2, cy + y2, cx + x, cy + y);
                }

                @Override
                public void curvetoQuadraticAbs(float x1, float y1, float x, float y) throws ParseException {
                    cx = x;
                    cy = y;
                    NodoTrayecto nodo = new NodoTrayecto(NodoTrayecto.TipoNodo.CURVA_CUBICA,
                            new Point2D.Double(x, y),
                            new Point2D.Double(x1, y1),
                            null);
                    agregar(nodo);
                }

                @Override
                public void curvetoQuadraticRel(float x1, float y1, float x, float y) throws ParseException {
                    curvetoQuadraticAbs(cx + x1, cy + y1, cx + x, cy + y);
                }

                @Override
                public void curvetoQuadraticSmoothAbs(float x, float y) throws ParseException {
                    cx = x;
                    cy = y;
                    NodoTrayecto ultimoNodo = nodos.getLast();
                    Point2D simetrico = ultimoNodo.getTipo() == NodoTrayecto.TipoNodo.CURVA_CUADRATICA ? ultimoNodo.getSimetricaPosicionControl1() : null;

                    float x1 = (float) (simetrico != null ? simetrico.getX() : x);
                    float y1 = (float) (simetrico != null ? simetrico.getY() : y);
                    curvetoQuadraticAbs(x1, y1, x, y);
                }

                @Override
                public void curvetoQuadraticSmoothRel(float x, float y) throws ParseException {
                    curvetoQuadraticSmoothAbs(cx + x, cy + y);
                }
                
                @Override
                public void arcAbs(float rx, float ry, float xAxisRotation, boolean largeArcFlag, boolean sweepFlag, float x, float y) throws ParseException {
                    cx = x;
                    cy = y;
                    NodoTrayecto nodo = new NodoTrayecto(NodoTrayecto.TipoNodo.ARCO,
                            rx, ry, xAxisRotation, largeArcFlag, sweepFlag, new Point2D.Double(x, y));
                    agregar(nodo);
                }
                
                @Override
                public void arcRel(float rx, float ry, float xAxisRotation, boolean largeArcFlag, boolean sweepFlag, float x, float y) throws ParseException {
                    arcAbs(rx, ry, xAxisRotation, largeArcFlag, sweepFlag, cx + x, cy + y);
                }


                @Override
                public void closePath() throws ParseException {
                    NodoTrayecto nodo = new NodoTrayecto(NodoTrayecto.TipoNodo.CERRAR, null);
                    agregar(nodo);
                }
            };
            parser.setPathHandler(handler);
            parser.parse(datos.getTexto());
        } catch (Exception ex) {
            ex.printStackTrace();
            System.out.println("datos: " + datos);
        }
    }
}
